package org.apache.commons.graph.model

import org.apache.commons.graph.MutableGraph
import org.apache.commons.graph.VertexPair
import org.apache.commons.graph.bean.DefaultEdge

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

typealias BaseSampleMutableGraph<V> = BaseMutableGraph<V, DefaultEdge<V>>


/**
 * Basic abstract in-memory based of a simple mutable [org.apache.commons.graph.Graph] implementation.
 *
 * This class is NOT thread safe!
 *
 * @param <V> the Graph vertices type
 * @param <E> the Graph edges type
</E></V> */
abstract class BaseMutableGraph<V, E> : BaseGraph<V, E>(), MutableGraph<V, E> {
    /**
     * {@inheritDoc}
     */
    override fun addEdge(head: V, e: E, tail: V) {
        checkGraphCondition(head != null, "Null head Vertex not admitted")
        checkGraphCondition(e != null, "Impossible to add a null Edge in the Graph")
        checkGraphCondition(tail != null, "Null tail Vertex not admitted")
        checkGraphCondition(containsVertex(head), "Head Vertex '$head' not present in the Graph" )
        checkGraphCondition(containsVertex(tail), "Head Vertex '$tail' not present in the Graph" )
        checkGraphCondition(getEdge(head, tail) == null, "Edge $e is already present in the Graph")

        allEdges.add(e)

        internalAddEdge(head, e, tail)

        decorateAddEdge(head, e, tail)
    }

    /**
     * {@inheritDoc}
     */
    override fun addVertex(v: V) {
        checkGraphCondition(v != null, "Impossible to add a null Vertex to the Graph")
        checkGraphCondition(!containsVertex(v), "Vertex '$v' already present in the Graph")

        adjacencyList[v] = LinkedHashSet()

        decorateAddVertex(v)
    }

    /**
     * Executes additional actions to edge that will be added
     * @param head the head vertex
     * @param e the edge
     * @param tail the tail vertex
     */
    protected abstract fun decorateAddEdge(head: V, e: E, tail: V)

    /**
     * Executes additional actions to vertex that will be added
     * @param v the vertex
     */
    protected abstract fun decorateAddVertex(v: V)

    /**
     * Executes additional actions to edge that will be removed
     * @param e the edge
     */
    protected abstract fun decorateRemoveEdge(e: E)

    /**
     * Executes additional actions to vertex that will be removed
     * @param v the vertex
     */
    protected abstract fun decorateRemoveVertex(v: V)

    /**
     * Performs the internal operations to add the edge
     *
     * @param head the head vertex
     * @param e the edge
     * @param tail the tail vertex
     */
    protected fun internalAddEdge(head: V, e: E, tail: V) {
        adjacencyList[head]!!.add(tail)

        val vertexPair = VertexPair(head, tail)
        indexedEdges[vertexPair] = e

        if (!indexedVertices.containsKey(e)) {
            indexedVertices[e] = vertexPair
        }
    }

    /**
     * Performs the internal operations to remove the edge.
     *
     * @param head the head vertex
     * @param e the edge
     * @param tail the tail vertex
     */
    protected fun internalRemoveEdge(head: V, e: E, tail: V) {
        val vertexPair = VertexPair(head, tail)
        indexedVertices.remove(e)
        indexedEdges.remove(vertexPair)
        adjacencyList[vertexPair.head]?.remove(vertexPair.tail)
    }

    /**
     * {@inheritDoc}
     */
    override fun removeEdge(e: E) {
        checkGraphCondition(e != null, "Impossible to remove a null Edge from the Graph")
        checkGraphCondition(containsEdge(e), "Edge '%s' not present in the Graph", e)
        val vertexPair = getVertices(e)
        decorateRemoveEdge(e)
        internalRemoveEdge(vertexPair!!.head, e, vertexPair.tail)
        allEdges.remove(e)
    }

    /**
     * {@inheritDoc}
     */
    override fun removeVertex(v: V) {
        checkGraphCondition(v != null, "Impossible to remove a null Vertex from the Graph")
        checkGraphCondition(containsVertex(v), "Vertex '%s' not present in the Graph", v)

        for (tail in adjacencyList[v]!!) {
            indexedEdges.remove(VertexPair(v, tail))
        }
        adjacencyList.remove(v)

        decorateRemoveVertex(v)
    }

}
